#!/usr/bin/env python
#return data with HTTP/1.0
import socket, thread, select,traceback,sys,os
import datetime
import threading
from time import sleep
import os
import httplib

__version__ = '0.1.0 Draft 1'
BUFLEN = 8192
VERSION = 'Python Proxy/'+__version__
HTTPVER = 'HTTP/1.1'
listen = ('',8081);
isencode = False

#monkey patch for httplib.HTTPMessage
#because the httplib.HTTPMessage return a lower version of
#headers.....we need the nomail version of them. 
class _HTTPMessage(httplib.HTTPMessage):
  def isheader(self, line):
    i = line.find(':')
    if i > 0:
      return line[:i]
    return None
  def __init__(self, fp, seekable = 1):
    import rfc822
    rfc822.Message.__init__(self, fp, seekable)
    self.tdict = dict(self.dict)
    self.dict={}
    for x,y in self.tdict.iteritems():
      self.dict[x.lower()] = y
  def items(self):
      return self.tdict.items() 
  
  def addheader(self, key, value):
    prev = self.dict.get(key)
    if prev is None:
      self.dict[key] = value
    else:
      self.dict[key] = "%s\r\n%s: %s"%(prev,key,value)

httplib.HTTPMessage = _HTTPMessage

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class ExceptGetout(BaseException):
  pass
#package:data_size(hex)\n, encode data
#function here donot care about data_size
#encode data that send to serverA
def encode(data):
  return data;

#decode data that recv from serverA
def decode(data):
  return data

def deadloop(s):
  print s,os.urandom(3).encode('base64')
  
class ConnectionHandler:
    def __init__(self, connection, address, timeout):
        self.client = connection
        self.client_buffer = ''
        self.target_buffer = ''
        self.rawbuffer = ''
        self.timeout = timeout
        self.lasthost = '';
        self.target = None
        try:
          self.handle()
        except socket.error:
          temp = ''.join(traceback.format_exc().split('\n')[-2:])
          try:
            self.send_error(502,'socket error:\n'+temp)
          except ExceptGetout:pass
        except ExceptGetout:
          pass
        except SystemExit:
          pass
        except:
          temp = traceback.format_exc()
          traceback.print_exc();
          try:
            self.send_error(502,'error Unhandle!\n'+temp)
          except ExceptGetout:pass
          
        try:
          self.client.shutdown(socket.SHUT_RDWR)
          self.client.close()
          self.target.shutdown(socket.SHUT_RDWR)
          self.target.close()
        except:
          pass
    
    def handle(self):
        self.client_headers = None
        self.client_headersize = 0;
        self.client_bodysize = 0;
        self.target_headers = None
        self.target_headersize = 0;
        self.target_bodysize = 0;
        self.keepalive = 0;
        #GET http://www.google.com/ HTTP/1.1
        self.method, self.path, self.protocol, self.client_headers, buf ,\
                self.client_headersize = self.get_base_header(self.client)
        self.client_buffer += buf
        #print self.client_buffer
        if self.method=='CONNECT':
            self.method_CONNECT()
        elif self.method in ('OPTIONS', 'GET', 'HEAD', 'POST', 'PUT',
                             'DELETE', 'TRACE'):
            self.method_others()

    def get_base_header(self,sock):
        headers = None
        buf=''
        while 1:
            #deadloop(88)
            tmp = sock.recv(BUFLEN)
            if tmp=='':self.send_error(400,"client socket closed ,before find headers");
            buf += tmp
            end = buf.find('\r\n\r\n')#get the request and header
            if end!=-1:
                break
        headersize = end + 4;#'\r\n\r\n'4 bytes
        end = buf.find("\n")
        line = buf[:end+1];#get the request
        data = line.split()
        if len(data)>3:
          data[2] = ' '.join(data[2:])
          data = data[:3]
        headers = self.get_header(buf[end+1:headersize])
        #print '-'*78
        #print line, #debug
        #print headers
        #get the headers
        return data+[headers,buf,headersize]

    def get_header(self,buf):
        hdict = {};
        headerline = buf.split('\n');
        for x in headerline:
          h=x.split(':');
          if len(h)>=2:
            h[1]=':'.join(h[1:])
            h[1]=h[1].strip('\r\n ')
            hdict[h[0]]=h[1];
        return hdict;

    def _connect_target(self, host):
        i = host.find(':')
        if i!=-1:
            port = int(host[i+1:])
            host = host[:i]
        else:
            port = 80
        try:
          (soc_family, _, _, _, address) = socket.getaddrinfo(host, port)[0]
          self.target = socket.socket(soc_family)
          self.target.connect(address)
        except socket.error,s:
          #print '"'*78
          #print 's=',s,'type(s)=',type(s)
          self.send_error(502,"Unable to resolve hostname,\nmay be something wrong with\
your input hostname or DNS servers of proxy server.\nreason:"+traceback.format_exc().split('\n')[-2])        

    def method_CONNECT(self):
        #connect target
        self._connect_target(self.path)
        self.client.send(HTTPVER+' 200 Connection established\n'+
                         'Proxy-agent: %s\n\n'%VERSION)
        self.client_buffer = ''
        #1 recv from client and send to target
        #2 recv from target and send to client
        self._read_write()
        
    client_del_headers = ('connection', 'proxy-connection', 'proxy-authorization',
                          'host', 'vary', 'via', 'x-forwarded-for')
    target_del_headers = ("transfer-encoding")
    def method_others(self):
        #connect target
        self.path = self.path[7:]
        i = self.path.find('/')
        host = self.path[:i]        
        path = self.path[i:]
        #self._connect_target(host)
        #use httplib to get response
        if (self.client_headers.get("Proxy-Connection",0)=='keep-alive'):
          self.keepalive = 1;
        else: self.keepalive = 0;
        #speed:select client and target.
        #recv a request and send it to target
        self.client_bodysize = int(self.client_headers.get("Content-Length",0));
        totalsize = self.client_headersize + self.client_bodysize
        while (len(self.client_buffer)<totalsize):
          #deadloop(147)
          self.client_buffer += self.client.recv(BUFLEN)
        body = self.client_buffer[self.client_headersize:totalsize]
        
        h1 = httplib.HTTPConnection(host,timeout = self.timeout)

        h1.putrequest(self.method, path, skip_host = 0, skip_accept_encoding = 0)
        self.client_headers['Connection'] = 'close'
        self.client_headers['Accept-Encoding']= 'identity'
        for x,y in self.client_headers.iteritems():
			    if x.lower() in self.client_del_headers:continue
			    #print x,': ',y
			    h1.putheader(x,y)
        h1.endheaders()
        h1.send(body)
			  
        r1 = h1.getresponse();
        if (r1.version==10):self.target_version = 'HTTP/1.0'
        elif (r1.version==11):self.target_version = 'HTTP/1.1'
        tosend = '%s %s %s\r\n'%(self.target_version,r1.status,r1.reason)
        self.target_headers = r1.getheaders()
        for x,y in self.target_headers:
          if (x.lower() in self.target_del_headers):continue
          if (x.lower()=='connection'):tosend += 'Connection: close'
          tosend += x+': '+y+'\r\n';
        tosend +='\r\n'
        self.client.send(tosend);
        while(1):
          tosend = r1.read(BUFLEN)
          if (tosend==''):break
          self.client.send(tosend);        
        h1.close()
    
    #function here need to care about data size,at last one chunked
    def recv_decode(self,size=-1):
    #try to return one chunked.
    #donot care about recv how many bytes
      while (1):
        i = self.rawbuffer.find('\n')
        if i != -1:
          try:
            size = int(rawbuffer[:i],16):
            if ((i+1+size)<=len(self.rawbuffer)):
              return decode(rawbuffer[i+1:i+1+size])
          except ValueError:
            self.client.send("\n\nWrong format!\n");
            raise ExceptGetout
        tmp = self.client.recv(size);
        if (tmp==''):
            self.client.send("\n\nWrong format!\n");
            raise ExceptGetout
        self.rawbuffer +=tmp

    def send_encode(self,data):
      data = encode(data);
      length=hex(len(data)).strip('L')[2:]
      self.client.send(length+'\n'+data)
    
    def _read_write(self):
        time_out_max = self.timeout/3
        socs = [self.client, self.target]
        count = 0
        while 1:
            #deadloop(229)
            count += 1
            (recv, _, error) = select.select(socs, [], socs, 3)
            if error:
                break
            if recv:
                for in_ in recv:
                    data = in_.recv(BUFLEN)
                    if in_ is self.client:
                        out = self.target
                    else:
                        out = self.client
                    if data:
                        #print data
                        out.send(data)
                        count = 0
            if count == time_out_max:
                break

    def send_error(self, code, message=None):
        self.keepalive = 0;
        try:
            short, long = self.responses[code]
        except KeyError:
            short, long = '???', '???'
        if message is None:
            message = short
        explain = long
        tosend = "%s %d %s\r\n" %(HTTPVER, code, short)
        tosend += 'Server: %s\r\n'%VERSION
        tosend +='Date: %s\r\n'  %str(datetime.datetime.now())
        tosend +="Content-Type: text/html\r\n"
        tosend +="Connection: close\r\n"
        tosend +='\r\n'
        send_body=0;
        try:
          if self.method != 'HEAD' and code >= 200 and code not in (204, 304):
            send_body=1;
        except AttributeError:
          send_body=1;
        if send_body==1: tosend += """\
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code %(code)d.
<p>Message: %(message)s.
<p>Error code explanation: %(code)s = %(explain)s.
</body>
"""%{'code': code, 'message': _quote_html(message), 'explain': explain}
        try:
          self.client.send(tosend)
        except:
          pass
        raise ExceptGetout;

    responses = {
        100: ('Continue', 'Request received, please continue'),
        101: ('Switching Protocols',
              'Switching to new protocol; obey Upgrade header'),

        200: ('OK', 'Request fulfilled, document follows'),
        201: ('Created', 'Document created, URL follows'),
        202: ('Accepted',
              'Request accepted, processing continues off-line'),
        203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
        204: ('No Content', 'Request fulfilled, nothing follows'),
        205: ('Reset Content', 'Clear input form for further input.'),
        206: ('Partial Content', 'Partial content follows.'),

        300: ('Multiple Choices',
              'Object has several resources -- see URI list'),
        301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
        302: ('Found', 'Object moved temporarily -- see URI list'),
        303: ('See Other', 'Object moved -- see Method and URL list'),
        304: ('Not Modified',
              'Document has not changed since given time'),
        305: ('Use Proxy',
              'You must use proxy specified in Location to access this '
              'resource.'),
        307: ('Temporary Redirect',
              'Object moved temporarily -- see URI list'),

        400: ('Bad Request',
              'Bad request syntax or unsupported method'),
        401: ('Unauthorized',
              'No permission -- see authorization schemes'),
        402: ('Payment Required',
              'No payment -- see charging schemes'),
        403: ('Forbidden',
              'Request forbidden -- authorization will not help'),
        404: ('Not Found', 'Nothing matches the given URI'),
        405: ('Method Not Allowed',
              'Specified method is invalid for this resource.'),
        406: ('Not Acceptable', 'URI not available in preferred format.'),
        407: ('Proxy Authentication Required', 'You must authenticate with '
              'this proxy before proceeding.'),
        408: ('Request Timeout', 'Request timed out; try again later.'),
        409: ('Conflict', 'Request conflict.'),
        410: ('Gone',
              'URI no longer exists and has been permanently removed.'),
        411: ('Length Required', 'Client must specify Content-Length.'),
        412: ('Precondition Failed', 'Precondition in headers is false.'),
        413: ('Request Entity Too Large', 'Entity is too large.'),
        414: ('Request-URI Too Long', 'URI is too long.'),
        415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
        416: ('Requested Range Not Satisfiable',
              'Cannot satisfy request range.'),
        417: ('Expectation Failed',
              'Expect condition could not be satisfied.'),

        500: ('Internal Server Error', 'Server got itself in trouble'),
        501: ('Not Implemented',
              'Server does not support this operation'),
        502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
        503: ('Service Unavailable',
              'The server cannot process the request due to a high load'),
        504: ('Gateway Timeout',
              'The gateway server did not receive a timely response'),
        505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
        }
def _quote_html(html):
    return html.replace("&", "&amp;").replace("<", "&lt;").replace(
        ">", "&gt;").replace("\n","<p>")
isserverlive = 1;
__is_shut_down = 0;
def start_server(host='localhost', port=8080, IPv6=False, timeout=60,
                  handler=ConnectionHandler):
    global isserverlive,__is_shut_down
    if IPv6==True:
        soc_type=socket.AF_INET6
    else:
        soc_type=socket.AF_INET
    soc = socket.socket(soc_type)
    soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    soc.bind((host, port))
    print "Serving on %s:%d."%(host, port)#debug
    soc.listen(0)
    __is_shut_down = 0;
    while 1:
      arg=soc.accept()+(timeout,)
      if (isserverlive==0):
        arg[0].close()
        break;
      t = threading.Thread(target = handler,args = arg)
      t.setDaemon (1)
      t.start() 
      del t
    soc.shutdown(socket.SHUT_RDWR)
    soc.close()
    print 'start_server ended'
    __is_shut_down = 1;

def shutdown(host='localhost', port=8080,IPv6=False):
    global isserverlive,__is_shut_down
    isserverlive = 0;
    if IPv6==True:
        soc_type=socket.AF_INET6
    else:
        soc_type=socket.AF_INET
    target = socket.socket(soc_type)
    try:
      target.connect((host, port))
      target.close();
    except:
      pass
    while(1):
      sleep(0.2)
      if (__is_shut_down==1):return;
    
    
def taskother():
    from time import sleep
    try:
      while(1):sleep(1)
    except KeyboardInterrupt:
      shutdown(listen[0],listen[1])

if __name__ == '__main__':
  try:
    path = os.path.dirname(os.path.abspath(sys.argv[0]))
    execfile(os.path.join(path,'serverB.conf') )
  except:
    traceback.print_exc();
  print listen
  threading.Thread(target = start_server,args = (listen[0],listen[1])).start()
  taskother();
